package ru.dc.util;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.VFS;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import ru.dc.Application;
import ru.dc.object.DownloadItem;
import ru.dc.object.DownloadSource;
import ru.dc.object.FileInfo;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Функции для работы со списками файлов пользователей.
 * Список файлов пользователя - это специальный файл-архив files.xml.bz2, внутри которого лежит xml-документ
 * fileslist.xml, описывающий иерархию файлов в расшаренных директориях пользователя. 
 */
public class FilelistUtils {

    private static Log logger = LogFactory.getLog(FilelistUtils.class);

    /**
     * Генерирует уникальное имя файла, под которым следует сохранять список файлов пользователя.
     * @param source источник, с которого скачивается список файлов пользователя
     * @return имя файла
     */
    public static String generateIndividualFilelistName(DownloadSource source) {
        return source.getNick()+"-files-"+source.getHub().getAddress()+":"+source.getHub().getPort()+".xml.bz2";
    }

    /**
     * Возвращает директорию, в которую следует загружать списки файлов. Директория находится внутри
     * домашней директории приложения. Если директории не существует. она будет создана.
     * @return директория для хранения загруженных  списков файлов пользователей.
     */
    public static File getFilelistsDir() {
        File parentDir = new File(Application.getApplication().getApplicationHomeDir(), "bzlists");
        if (!parentDir.exists()) {
            parentDir.mkdir();
        }
        return parentDir;
    }


    /**
     * Разбирает скачанный список файлов пользователя и генерирует из него дерево объектов FileInfo.
     * @param filelist файл, сожержащий списко файлов
     * @return корень дерева объектов FileInfo
     * @throws IOException
     * @throws JDOMException
     */
    public static FileInfo getFiles(File filelist) throws IOException, JDOMException {
        FileSystemManager fsManager = VFS.getManager();
        FileObject fileList = fsManager.resolveFile("bz2:file://" + filelist.getAbsolutePath() + "!filelist.xml");
        InputStream in = fileList.getContent().getInputStream();
        // Build the document with SAX and Xerces, no validation
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();//<FileListing
        FileInfo rootFile = new FileInfo();
        rootFile.setName(root.getAttributeValue("Base"));
        List<Element> childs = root.getChildren();

        for (Element elem : childs) {
            loadChildren(rootFile, elem);
        }
        

        return rootFile;

    }

    /**
     * Вспомогательная рекурсивная процедура для выставления значений полей и загрузки дочерних элементов FileInfo
     * узла дерева.
     * @param parenFileInfo родительский узел дерева элементов FileInfo
     * @param childElement элемент xml-документа, для которого будет создан дочерний элемент fileInfo
     */
    private static void loadChildren(FileInfo parenFileInfo, Element childElement) {
        FileInfo childFileInfo = new FileInfo();
        childFileInfo.setName(childElement.getAttributeValue("Name"));
        if ("Directory".equals(childElement.getName())) {
            childFileInfo.setDirectory(true);
        }
        else {
            childFileInfo.setDirectory(false);
            childFileInfo.setTth(childElement.getAttributeValue("TTH"));
            childFileInfo.setLength(new Long(childElement.getAttributeValue("Size")));
        }
        parenFileInfo.addChild(childFileInfo);

        if (childFileInfo.isDirectory()) {
            List<Element> subChildren = childElement.getChildren();
            for (Element subChildElenent : subChildren) {
                loadChildren(childFileInfo, subChildElenent);
            }
        }
    }

    /**
     * Проверяет, является ли данный элемент загрузки списком файлов пользователя.
     * @param e элемент загрузки
     * @return true, если данный элемент загрузки - список файлов, иначе false
     */
    public static boolean isFilelist(DownloadItem e) {
        return e.getFilename().equals("files.xml.bz2");
    }

    /**
     * Удаляет список файлов пользователя. Данный метод следует вызывать при закрытии вкладки со списком файлов
     * пользователя.
     * @param source источник файлов
     */
    public static void removeUsersFilelist(DownloadSource source) {
        File parent = getFilelistsDir();
        String usersFilename = generateIndividualFilelistName(source);
        File usersFilelist = new File(parent, usersFilename);
        if (usersFilelist.exists()) {
            boolean res = usersFilelist.delete();
            if (res) {
                logger.debug("Deleted user's filelist: "+usersFilelist);
            }
            else {
                logger.warn("Can't delete user's filelist: "+usersFilelist);
            }
        }
    }




    /**
     * Генерирует путь до файла от корня иерархии файлов. К примеру,
     * <pre>
     * root <-- фиктивная голова
     *   dir1
     *     file1
     * </pre>
     * для file1 будет сгенерирован путь /dir1/file1, то есть фиктивная голова не учитывается,
     * имена файлов разделяются символом '\'.
     * @param fileInfo файл
     * @return пусть до файла от корня иерархии файлов
     */
    public static String generatePathFromRoot(FileInfo fileInfo) {
        java.util.List<FileInfo> path = new ArrayList<FileInfo>();
        FileInfo pos = fileInfo;
        while (pos.getParent()!=null) {
            path.add(pos);
            pos = pos.getParent();
        }

        Collections.reverse(path);
        String fullPath = StringUtils.join(path, "\\");
        return fullPath;
    }

}
